/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.oa.poa ;

import java.util.Set ;

import org.omg.CORBA.SystemException ;

import org.omg.PortableServer.ServantActivator ;
import org.omg.PortableServer.Servant ;
import org.omg.PortableServer.ServantManager ;
import org.omg.PortableServer.ForwardRequest ;
import org.omg.PortableServer.POAPackage.WrongPolicy ;
import org.omg.PortableServer.POAPackage.ObjectNotActive ;
import org.omg.PortableServer.POAPackage.ServantNotActive ;
import org.omg.PortableServer.POAPackage.ObjectAlreadyActive ;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive ;
import org.omg.PortableServer.POAPackage.NoServant ;

import com.sun.corba.se.impl.orbutil.concurrent.SyncUtil ;
import com.sun.corba.se.impl.orbutil.ORBUtility ;
import com.sun.corba.se.impl.orbutil.ORBConstants ;

import com.sun.corba.se.impl.oa.NullServantImpl ;

import com.sun.corba.se.impl.javax.rmi.CORBA.Util ;

import com.sun.corba.se.spi.oa.OAInvocationInfo ;
import com.sun.corba.se.spi.oa.NullServant ;

/** Implementation of POARequesHandler that provides policy specific
 * operations on the POA.
 */
public class POAPolicyMediatorImpl_R_USM extends POAPolicyMediatorBase_R {
    protected ServantActivator activator ;

    POAPolicyMediatorImpl_R_USM( Policies policies, POAImpl poa ) 
    {
	// assert policies.retainServants() 
	super( policies, poa ) ;
	activator = null ;

	if (!policies.useServantManager())
	    throw poa.invocationWrapper().policyMediatorBadPolicyInFactory() ;
    }
   
    /* This handles a rather subtle bug (4939892).  The problem is that
     * enter will wait on the entry if it is being etherealized.  When the
     * deferred state transition completes, the entry is no longer in the
     * AOM, and so we need to get a new entry, otherwise activator.incarnate
     * will be called twice, once for the old entry, and again when a new
     * entry is created.  This fix also required extending the FSM StateEngine
     * to allow actions to throw exceptions, and adding a new state in the
     * AOMEntry FSM to detect this condition.
     */
    private AOMEntry enterEntry( ActiveObjectMap.Key key )
    {
	AOMEntry result = null ;
	boolean failed ;
	do {
	    failed = false ;
	    result = activeObjectMap.get(key) ;

	    try {
		result.enter() ;
	    } catch (Exception exc) {
		failed = true ;
	    }
	} while (failed) ;

	return result ;
    }

    protected java.lang.Object internalGetServant( byte[] id, 
	String operation ) throws ForwardRequest
    { 
	if (poa.getDebug()) {
	    ORBUtility.dprint( this, 
		"Calling POAPolicyMediatorImpl_R_USM.internalGetServant " + 
		"for poa " + poa + " operation=" + operation ) ;
	}

	try {
	    ActiveObjectMap.Key key = new ActiveObjectMap.Key( id ) ;
	    AOMEntry entry = enterEntry(key) ;
	    java.lang.Object servant = activeObjectMap.getServant( entry ) ;
	    if (servant != null) {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this, 
			"internalGetServant: servant already activated" ) ;
		}

		return servant ;
	    }

	    if (activator == null) {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this, 
			"internalGetServant: no servant activator in POA" ) ;
		}

		entry.incarnateFailure() ;
		throw poa.invocationWrapper().poaNoServantManager() ;
	    }
       
	    // Drop the POA lock during the incarnate call and
	    // re-acquire it afterwards.  The entry state machine
	    // prevents more than one thread from executing the
	    // incarnate method at a time within the same POA.
	    try {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this,
			"internalGetServant: upcall to incarnate" ) ;
		}

		poa.unlock() ;

		servant = activator.incarnate(id, poa);

		if (servant == null)
		    servant = new NullServantImpl( 
			poa.omgInvocationWrapper().nullServantReturned() ) ;
	    } catch (ForwardRequest freq) {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this,
			"internalGetServant: incarnate threw ForwardRequest" ) ;
		}

		throw freq ;
	    } catch (SystemException exc) {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this,
			"internalGetServant: incarnate threw SystemException " + exc ) ;
		}

		throw exc ;
	    } catch (Throwable exc) {
		if (poa.getDebug()) {
		    ORBUtility.dprint( this,
			"internalGetServant: incarnate threw Throwable " + exc ) ;
		}

		throw poa.invocationWrapper().poaServantActivatorLookupFailed( 
		    exc ) ;
	    } finally {
		poa.lock() ;

		// servant == null means incarnate threw an exception,
		// while servant instanceof NullServant means incarnate returned a 
		// null servant.  Either case is an incarnate failure to the 
		// entry state machine.
		if ((servant == null) || (servant instanceof NullServant)) {
		    if (poa.getDebug()) {
			ORBUtility.dprint( this, 
			    "internalGetServant: incarnate failed" ) ;
		    }

		    // XXX Does the AOM leak in this case? Yes,
		    // but the problem is hard to fix.  There may be
		    // a number of threads waiting for the state to change
		    // from INCARN to something else, which is VALID or
		    // INVALID, depending on the incarnate result.
		    // The activeObjectMap.get() call above creates an 
		    // ActiveObjectMap.Entry if one does not already exist,
		    // and stores it in the keyToEntry map in the AOM.
		    entry.incarnateFailure() ;
		} else {
		    // here check for unique_id policy, and if the servant
		    // is already registered for a different ID, then throw
		    // OBJ_ADAPTER exception, else activate it. Section 11.3.5.1
		    // 99-10-07.pdf
		    if (isUnique) {
			// check if the servant already is associated with some id
			if (activeObjectMap.contains((Servant)servant)) {
			    if (poa.getDebug()) {
				ORBUtility.dprint( this, 
				    "internalGetServant: servant already assigned to ID" ) ;
			    }

			    entry.incarnateFailure() ;
			    throw poa.invocationWrapper().poaServantNotUnique() ;
			}
		    }

		    if (poa.getDebug()) {
			ORBUtility.dprint( this, 
			    "internalGetServant: incarnate complete" ) ;
		    }

		    entry.incarnateComplete() ;
		    activateServant(key, entry, (Servant)servant);
		}
	    }

	    return servant ;
	} finally {
	    if (poa.getDebug()) {
		ORBUtility.dprint( this, 
		    "Exiting POAPolicyMediatorImpl_R_USM.internalGetServant " + 
		    "for poa " + poa ) ;
	    }
	}
    }

    public void returnServant()
    {
	OAInvocationInfo info = orb.peekInvocationInfo();
	byte[] id = info.id() ;
	ActiveObjectMap.Key key = new ActiveObjectMap.Key( id ) ;
	AOMEntry entry = activeObjectMap.get( key ) ;
	entry.exit() ;
    }

    public void etherealizeAll() 
    {	
	if (activator != null)  {
	    Set keySet = activeObjectMap.keySet() ;

	    // Copy the elements in the set to an array to avoid
	    // changes in the set due to concurrent modification
	    ActiveObjectMap.Key[] keys = 
		(ActiveObjectMap.Key[])keySet.toArray( 
		    new ActiveObjectMap.Key[ keySet.size() ] ) ;

	    for (int ctr=0; ctr<keySet.size(); ctr++) {
		ActiveObjectMap.Key key = keys[ctr] ;
		AOMEntry entry = activeObjectMap.get( key ) ;
		Servant servant = activeObjectMap.getServant( entry ) ;
		if (servant != null) {
		    boolean remainingActivations = 
			activeObjectMap.hasMultipleIDs(entry) ;

		    // Here we etherealize in the thread that called this 
		    // method, rather than etherealizing in a new thread 
		    // as in the deactivate case.  We still inform the 
		    // entry state machine so that only one thread at a 
		    // time can call the etherealize method.
		    entry.startEtherealize( null ) ;
		    try {
			poa.unlock() ;
			try {
			    activator.etherealize(key.id, poa, servant, true, 
				remainingActivations);
			} catch (Exception exc) {
			    // ignore all exceptions
			}
		    } finally {
			poa.lock() ;
			entry.etherealizeComplete() ;
		    }
		}
	    }
	}
    }

    public ServantManager getServantManager() throws WrongPolicy
    {
	return activator;
    }

    public void setServantManager( 
	ServantManager servantManager ) throws WrongPolicy
    {
	if (activator != null)
	    throw poa.invocationWrapper().servantManagerAlreadySet() ;

	if (servantManager instanceof ServantActivator)
	    activator = (ServantActivator)servantManager;
	else
	    throw poa.invocationWrapper().servantManagerBadType() ;
    }

    public Servant getDefaultServant() throws NoServant, WrongPolicy 
    {
	throw new WrongPolicy();
    }

    public void setDefaultServant( Servant servant ) throws WrongPolicy
    {
	throw new WrongPolicy();
    }

    class Etherealizer extends Thread {
	private POAPolicyMediatorImpl_R_USM mediator ;
	private ActiveObjectMap.Key key ;
	private AOMEntry entry ;
	private Servant servant ;
	private boolean debug ;

	public Etherealizer( POAPolicyMediatorImpl_R_USM mediator, 
	    ActiveObjectMap.Key key, AOMEntry entry, Servant servant, 
	    boolean debug )
	{
	    this.mediator = mediator ;
	    this.key = key ;
	    this.entry = entry;
	    this.servant = servant;
	    this.debug = debug ;
	}

	public void run() {
	    if (debug) {
		ORBUtility.dprint( this, "Calling Etherealizer.run on key " + 
		    key ) ;
	    }

	    try {
		try {
		    mediator.activator.etherealize( key.id, mediator.poa, servant, 
			false, mediator.activeObjectMap.hasMultipleIDs( entry ) );
		} catch (Exception exc) {
		    // ignore all exceptions
		}

		try {
		    mediator.poa.lock() ;

		    entry.etherealizeComplete() ;
		    mediator.activeObjectMap.remove( key ) ;

		    POAManagerImpl pm = (POAManagerImpl)mediator.poa.the_POAManager() ;
		    POAFactory factory = pm.getFactory() ;
		    factory.unregisterPOAForServant( mediator.poa, servant);
		} finally {
		    mediator.poa.unlock() ;
		}
	    } finally {
		if (debug) {
		    ORBUtility.dprint( this, "Exiting Etherealizer.run" ) ;
		}
	    }
	}
    } 

    public void deactivateHelper( ActiveObjectMap.Key key, AOMEntry entry, 
	Servant servant ) throws ObjectNotActive, WrongPolicy 
    {
	if (activator == null)
	    throw poa.invocationWrapper().poaNoServantManager() ;
	    
	Etherealizer eth = new Etherealizer( this, key, entry, servant, poa.getDebug() ) ;
	entry.startEtherealize( eth ) ;
    }

    public Servant idToServant( byte[] id ) 
	throws WrongPolicy, ObjectNotActive
    {
	ActiveObjectMap.Key key = new ActiveObjectMap.Key( id ) ;
	AOMEntry entry = activeObjectMap.get(key);

	Servant servant = activeObjectMap.getServant( entry ) ;
	if (servant != null)
	    return servant ;
	else
	    throw new ObjectNotActive() ;
    }
}
